#include<iostream>
#include<queue>
#include<mutex>
#include<thread>
#include<condition_variable>

template<class T>
class SafeQueue
{
public:
	SafeQueue() = default;
	~SafeQueue() = default;
	SafeQueue(const T& data) = delete;
	SafeQueue& operator = (const T & data) = delete; 
	SafeQueue(T&& data) = delete;
	SafeQueue(const T&& data) = delete;
	SafeQueue& operator = (const T && data) = delete;

	bool empty()
	{
		std::unique_lock<std::mutex> lck(mtx);
		return q.empty();
	}

	int size()
	{
		std::unique_lock<std::mutex> lck(mtx);
		return q.size();
	}

	void push(T& data)
	{
		std::unique_lock<std::mutex> lck(mtx);
		q.emplace(data);
	}
	void push(T&& data)
	{
		std::unique_lock<std::mutex> lck(mtx);
		q.emplace(std::move(data));
	}
	bool pop(T& data)
	{
		std::unique_lock<std::mutex> lck(mtx);
		if (q.empty())
		{
			return false;
		}
		data = q.front();
		q.pop();
		return true;
	}
private:
	std::queue<T> q;
	std::mutex mtx;
};

template<class T>
class ProducerConsumer
{
public:
	ProducerConsumer() :m_QueueMaxSize(20), m_ProducerThreads(2), m_ConsumerThreads(2), m_RunningStatus(true){
		intialize();
	}
	ProducerConsumer(int queueMaxSize, int ProducerNum, int ConsumerNum)
		:m_QueueMaxSize(queueMaxSize), m_ProducerThreads(ProducerNum), m_ConsumerThreads(ConsumerNum), m_RunningStatus(true){
		intialize();
	}
	~ProducerConsumer()
	{
		m_RunningStatus = false; 
		m_QueueNotFullcv.notify_all();
		m_QueueNotEmptycv.notify_all();

		for (auto& t : m_ProducerThreads)
		{
			if (t.joinable()) {
				t.join();
			}
		}
		for (auto& t : m_ConsumerThreads)
		{
			if (t.joinable()) {
				t.join();
			}
		}
	}
private:
	SafeQueue<T> m_Queue;
	int m_QueueMaxSize;
	std::condition_variable m_QueueNotFullcv;
	std::condition_variable m_QueueNotEmptycv;

	std::vector<std::thread> m_ProducerThreads;
	std::vector<std::thread> m_ConsumerThreads;

	std::mutex m_Mutex;
	std::atomic<bool> m_RunningStatus;

	void intialize()
	{
		for (size_t i = 0; i < m_ProducerThreads.size(); ++i)
		{
			m_ProducerThreads[i] = std::thread(&ProducerConsumer::Producer, this);
		}
		for (size_t i = 0; i < m_ConsumerThreads.size(); ++i)
		{
			m_ConsumerThreads[i] = std::thread(&ProducerConsumer::Consumer, this);
		}
	}

	bool isFull()
	{
		if (m_Queue.size() >= m_QueueMaxSize)
		{
			return true;
		}
		return false;
	}
	void Producer()
	{
		while (m_RunningStatus)
		{
			std::unique_lock<std::mutex> locker(m_Mutex);
			if (isFull())
			{
				std::cout << "Queue is full! Waiting for m_QueueNotFullcv" << std::endl;
				m_QueueNotFullcv.wait(locker);
			}
			if (!isFull())
			{
				T value = 3;
				m_Queue.push(value);
				m_QueueNotEmptycv.notify_one();
			}
		}
	}
	void Consumer()
	{
		while (m_RunningStatus)
		{
			std::unique_lock<std::mutex> locker(m_Mutex);
			if (m_Queue.empty())
			{
				std::cout << "Queue is empty! Waiting for m_QueueNotEmptycv" << std::endl;
				m_QueueNotEmptycv.wait(locker);
			}
			if (!m_Queue.empty())
			{
				T value;
				bool result = m_Queue.pop(value);
				value++;
				m_QueueNotFullcv.notify_one();
				std::cout << "result:" << value << std::endl;
			}
		}
	}
};


int main()
{
	ProducerConsumer<int> producerConsumer(100, 6, 6);
	while (1)
	{
		;
	}
	return 0;
}